#include "Tracer.h"
#include "Result.h"
#include "Ray.h"
#include "World.h"
#include "GlobalDefine.h"
#include "Object.h"
#include "Material.h"
#include <iostream>
#include <random>
#include <stack>
#include <array>
#include <typeinfo>

namespace mray{

	mray::vec3 NormalTracer::Trace(Ray& ray, int depth) const{
		Result result(m_pWorld);
		if( m_pWorld->Hit(ray, &result) == false )
			return m_pWorld->GetBgColor(ray.m_dir);
		//add a bias to avoid self cross pattern
		result.p += result.n * Bias;
		//emissive texture
		if(typeid(EmissiveMaterial).hash_code() == typeid(*result.pMtl).hash_code())
			return result.pMtl->Shade(BLACK, BLACK, result);
		//direct light
		vec3 color = m_pWorld->Illuminate(result);

		return color;
	}

	mray::vec3 PathTracer::Trace(Ray& ray, int depth) const
	{
		struct TraceResult
		{
			vec3 indirectFactor;
			vec3 directColor;
		};

		std::stack<TraceResult> traceStack;

		std::mt19937 device;
		std::array<float, 3> arr = {ray.m_dir.x*4096.f, ray.m_dir.y*4096.f ,ray.m_dir.z*4096.f};
		device.seed(std::seed_seq(arr.begin(), arr.end()));
		std::uniform_real_distribution<float> distribution(0.f, 1.f);

		Result result(m_pWorld);
		while(depth-- > 0){
			traceStack.push(TraceResult());
			if( m_pWorld->Hit(ray, &result) == false )
			{
				traceStack.top().directColor = m_pWorld->GetBgColor(ray.m_dir);
				traceStack.top().indirectFactor = BLACK;
				break;
			}
			if(typeid(EmissiveMaterial).hash_code() == typeid(*result.pMtl).hash_code())
			{
				traceStack.top().directColor = result.pMtl->GetColor(result.t);
				traceStack.top().indirectFactor = WHITE;
				break;
			}

			//now we need [0-1]^2 distribution
			vec2 uv(distribution(device), distribution(device));
			//adjust bias
			result.p += result.n * Bias;

			//some mtl is special so we need to handle

			//handle mirror with direct light
			if(typeid(MirrorMaterial).hash_code() == typeid(*result.pMtl).hash_code())
			{
				//mirror has no direct lighting
				traceStack.top().directColor = BLACK;
			}else{
				//direct lighting
				traceStack.top().directColor = m_pWorld->Illuminate(result);
			}

			//indirect lighting
			//indirectFactor including mtlColor, mtlBRDF, ligDir....
			float pdf = result.pMtl->Sample(result, uv, ray.m_dir);

			if(pdf > 0.0000001f){
				traceStack.top().indirectFactor = result.pMtl->Shade(ray.m_dir, WHITE, result);
				traceStack.top().indirectFactor /= pdf;
			}else{
				break;
			}

			//prepare for next step
			ray.m_ori = result.p;
			ray.m_mint = FLT_MAX;
		}

		vec3 color;
		while(traceStack.empty() == false){
			color ^= traceStack.top().indirectFactor;
			color += traceStack.top().directColor;
			traceStack.pop();
		}
		return color;
	}

}